package com.founder.gateway.component.transport.netty.extension.support;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.founder.gateway.component.transport.netty.exception.NettyException;
import com.founder.gateway.component.transport.netty.filter.NettyFilterChain;
import com.founder.gateway.component.transport.netty.http.NettyHttpRequest;
import com.founder.gateway.component.transport.netty.http.NettyHttpResponse;
import com.founder.gateway.orm.fzapi.bean.entity.ApiEntityEntity;
import com.founder.gateway.system.consts.ApiSetuErrorCode;
import com.founder.gateway.system.consts.Consts;
import com.founder.gateway.system.util.HttpNormUtil;
import com.founder.gateway.system.util.JSONUtil;
import com.founder.gateway.system.util.SOAPUtil;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.QueryStringEncoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.founder.gateway.system.util.HttpNormUtil.APPEND_UTF_8;

public class ProtocolNettyHttpFilter extends AbstractNettyHttpFilter {
    @Override
    public void doFilter(NettyHttpRequest request, NettyHttpResponse response, NettyFilterChain chain) throws IOException, NettyException {
        doRequestProtocol(request, response, chain);
        chain.doFilter(request, response);
        doResponseProtocol(response, chain);
    }

    private void doRequestProtocol(NettyHttpRequest request, NettyHttpResponse response, NettyFilterChain chain) throws NettyException {
        ApiEntityEntity apiEntityEntity = chain.getApiEntityEntity();
        String url = apiEntityEntity.getUrl();
        if (url == null) {
            throw new NettyException(ApiSetuErrorCode.API_PROTOCOL_URL_NULL);
        }
        if (Consts.IMPLE_MODE_WEBSERVICE.equalsIgnoreCase(apiEntityEntity.getImpleMode())) {
            try {
                // 从请求头中获取 soap 协议版本
                String protocol = request.headers().get(SOAPUtil.HEADER_PROTOCOL);
                // 如果请求没有指定版本则使用配置中的版本
                if (StringUtils.isBlank(protocol)) {
                    protocol = apiEntityEntity.getProtocol();
                }
                // 创建 soap 报文
                Document soapMessage = SOAPUtil.createSOAPMessage(protocol);
                Element root = soapMessage.getRootElement();
                // 添加 envelope 节点的额外的命名空间
                SOAPUtil.addXmlns(root, request.headers().getAll(SOAPUtil.HEADER_ENVELOPE_XMLNS));
                // 添加 envelope 节点的额外的属性
                SOAPUtil.addAttr(root, request.headers().getAll(SOAPUtil.HEADER_ENVELOPE_ATTR));
                // 解析 url （格式：url|action|response，response可选 例：http://webservice/api|getUser|getUserResponse）
                String[] uris = url.split("\\|", 3);
                if (uris.length < 2) {
                    throw new NettyException(ApiSetuErrorCode.API_PROTOCOL_URL_VERIFY);
                }
                request.setUri(uris[0]);
                // 创建 action 节点
                Element action = DocumentHelper.createElement(uris[1]);
                // 添加 action 节点的额外的命名空间
                SOAPUtil.addXmlns(action, request.headers().getAll(SOAPUtil.HEADER_ACTION_XMLNS));
                // 添加 action 节点的额外的属性
                SOAPUtil.addAttr(action, request.headers().getAll(SOAPUtil.HEADER_ACTION_ATTR));
                root.element("Body").add(action);

                // 存储 soap header 节点数据
                JSONObject headerJson = new JSONObject();
                // 存储 soap body 节点数据
                JSONObject actionJson = new JSONObject();
                // 解析 url 和 body 中的数据
                for (Map.Entry<String, List<String>> entry : request.getParameterMap().getMaps().entrySet()) {
                    String key = entry.getKey();
                    final boolean isHeader = key.startsWith("#");
                    final boolean isJson = key.endsWith("$");
                    key = key.substring(isHeader ? 1 : 0, key.length() - (isJson ? 1 : 0));
                    JSONObject json = isHeader ? headerJson : actionJson;
                    List<String> value = entry.getValue();

                    JSONArray array = new JSONArray();
                    if (value == null) {
                        continue;
                    } else if (isJson) {
                        for (String text : value) {
                            array.add(JSON.parse(text));
                        }
                    } else {
                        array.addAll(value);
                    }
                    if (json.containsKey(key)) {
                        Object data = json.get(key);
                        if (data instanceof JSONArray) {
                            array.addAll((JSONArray) data);
                        } else {
                            array.add(data);
                        }
                    }
                    json.put(key, array);
                }
                SOAPUtil.json2xml(headerJson, root.element("Header"), true);
                // 构造 soap action 参数
                SOAPUtil.json2xml(actionJson, action, true);
                // 回写请求体
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                XMLWriter writer = null;
                try {
                    writer = new XMLWriter(out);
                    writer.write(soapMessage);
                    request.replaceRequest(Unpooled.wrappedBuffer(out.toByteArray()));
                } finally {
                    if (writer != null) {
                        writer.close();
                    }
                }
                // 设置请求头
                request.headers().set(HttpHeaderNames.CONTENT_TYPE, SOAPUtil.getContentType(protocol));
            } catch (Exception e) {
                throw new NettyException(ApiSetuErrorCode.API_PROTOCOL_REQUEST_TRANSFER, e.getMessage());
            }
        } else if (Consts.IMPLE_MODE_RESTFUL.equalsIgnoreCase(apiEntityEntity.getImpleMode())) {
            Set<String> params = HttpNormUtil.getRestParams(url);
            if (!params.isEmpty()) {
                Map<String, List<String>> map = request.getParameterMap().getMaps();
                for (String target : params) {
                    String name = target.substring(1, target.length() - 1);
                    List<String> list = map.get(name);
                    if (list == null || list.isEmpty()) {
                        throw new NettyException(ApiSetuErrorCode.API_REQUEST_PARAM_VERIFY, name + HttpNormUtil.NOT_BLANK);
                    }
                    url = url.replace(target, list.get(0));
                    request.getParameterMap().getParam().remove(name);
                    request.getParameterMap().getBody().remove(name);
                }
            }
            if (!request.getParameterMap().getParam().isEmpty()) {
                QueryStringEncoder encoder = new QueryStringEncoder(url);
                for (Map.Entry<String, List<String>> entry : request.getParameterMap().getParam().entrySet()) {
                    for (String value : entry.getValue()) {
                        encoder.addParam(entry.getKey(), value);
                    }
                }
                url = encoder.toString();
            }
            if (!request.getParameterMap().getBody().isEmpty()) {
                try {
                    HttpPostRequestEncoder encoder = new HttpPostRequestEncoder(request, true);
                    HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(request);
                    for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
                        if (data.getHttpDataType() != InterfaceHttpData.HttpDataType.Attribute) {
                            encoder.addBodyHttpData(data);
                        }
                    }
                    for (Map.Entry<String, List<String>> entry : request.getParameterMap().getBody().entrySet()) {
                        for (String value : entry.getValue()) {
                            encoder.addBodyAttribute(entry.getKey(), value);
                        }
                    }
                    encoder.finalizeRequest();
                    request.replaceRequest(encoder.readChunk(ByteBufAllocator.DEFAULT).content());
                } catch (Exception e) {
                    throw new NettyException(ApiSetuErrorCode.API_PROTOCOL_REQUEST_TRANSFER);
                }
            }
            request.setUri(url);
        } else {
            throw new NettyException(ApiSetuErrorCode.API_PROTOCOL_UNKNOWN, apiEntityEntity.getImpleMode());
        }
    }

    private void doResponseProtocol(NettyHttpResponse response, NettyFilterChain chain) throws NettyException {
        ApiEntityEntity apiEntityEntity = chain.getApiEntityEntity();
        if ("WebService".equalsIgnoreCase(apiEntityEntity.getImpleMode())) {
            try {
                ByteArrayInputStream in = new ByteArrayInputStream(response.getContent());
                SAXReader reader = new SAXReader();
                Document document = reader.read(in);
                JSONObject json = new JSONObject();
                SOAPUtil.xml2json(document.getRootElement(), json, false);
                json = JSONUtil.jsonPeeling(json, "Envelope", "Body");
                if (json != null) {
                    if (json.containsKey("Fault")) {
                        json = (JSONObject) json.get("Fault");
                    } else {
                        String[] names;
                        String[] uris = apiEntityEntity.getUrl().split("\\|", 3);
                        if (uris.length == 3) {
                            names = uris[2].split("/");
                        } else {
                            names = new String[]{uris[1] + "Response"};
                        }
                        json = JSONUtil.jsonPeeling(json, names);
                    }
                }
                if (json == null) {
                    throw new NettyException(ApiSetuErrorCode.API_PROTOCOL_RESPONSE_TRANSFER);
                } else {
                    response.setContent(JSON.toJSONBytes(json));
                    response.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
                    response.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON + APPEND_UTF_8);
                }
            } catch (Exception e) {
                throw new NettyException(ApiSetuErrorCode.API_PROTOCOL_RESPONSE_TRANSFER, e.getMessage());
            }
        }
    }
}
